﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.IO;
using nwrole.lib;

namespace nwrole.service
{
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
    public class Supervisor : ISupervisor
    {
        #region Services
        public nwrole.lib.Solution.Service GetService(Guid serviceId)
        {
            return Configuration.ServiceWithId(serviceId);
        }

        public nwrole.lib.Solution.Service[] ListServices ()
        {
            return Configuration.Services.ToArray();
        }

        public nwrole.lib.Solution.Service[] ListServicesInSolution (Guid solutionId)
        {
            return Configuration.ServicesInSolution(solutionId).ToArray();
        }
        #endregion

        #region Plugins
        public Result AddPlugin (byte[] data)
        {
            try
            {
                PluginBundle bundle = new PluginBundle(data);
                EventLogger.LogInformation("Supervisor.AddPlugin", String.Format("Adding plugin '{0}'.", bundle.Id));

                if (Configuration.PluginWithId(bundle.Id) != null)
                {
                    EventLogger.LogError("Supervisor.AddPlugin", String.Format("Plugin '{0}' already exists.", bundle.Id));
                    return Result.PluginIdAlreadyExists;
                }

                StorageUtil.StorePlugin(bundle);
                Plugin plugin = new Plugin
                {
                    Id = bundle.Id,
                    Name = bundle.Name,
                    Description = bundle.Description,
                    Author = bundle.Author,
                    Assembly = bundle.Assembly,
                    ClassName = bundle.ClassName,
                    Created = bundle.Created,
                    Updated = bundle.Updated,
                    Configuration = bundle.Configuration,
                    Added = DateTime.Now
                };
                Configuration.Plugins.Add(plugin);
                Configuration.Save();

                EventLogger.LogInformation("Supervisor.AddPlugin", String.Format("Added plugin '{0}'.", bundle.Id));

                bundle.Dispose();
            }
            catch (Exception exception)
            {
                EventLogger.LogException("Supervisor.AddPlugin", exception);
                return Result.UnknownError;
            }
            return Result.Success;
        }

        public Result RemovePlugin (Guid pluginId)
        {
            try
            {
                // TODO: Verify no solutions are running this plugin

                EventLogger.LogInformation("Supervisor.RemovePlugin", String.Format("Removing plugin '{0}'.", pluginId));

                if (Configuration.PluginWithId(pluginId) == null)
                {
                    EventLogger.LogError("Supervisor.RemovePlugin", String.Format("Plugin '{0}' does not exist.", pluginId));
                    return Result.PluginIdUnavailable;
                }

                // Stop all instances

                StorageUtil.DeletePlugin(pluginId);
                Configuration.Plugins.Remove(Configuration.PluginWithId(pluginId));
                Configuration.Save();

                EventLogger.LogInformation("Supervisor.RemovePlugin", String.Format("Plugin '{0}' removed.", pluginId));
            }
            catch (Exception exception)
            {
                EventLogger.LogException("Supervisor.RemovePlugin", exception);
                return Result.UnknownError;
            }
            return Result.Success;
        }

        public Result UpdatePlugin (byte[] data)
        {
            try
            {
                PluginBundle bundle = new PluginBundle(data);
                EventLogger.LogInformation("Supervisor", String.Format("Updating plugin '{0}'.", bundle.Id));

                if (Configuration.PluginWithId(bundle.Id) == null)
                {
                    EventLogger.LogError("Supervisor.UpdatePlugin", String.Format("Plugin '{0}' does not exist.", bundle.Id));
                    return Result.PluginIdUnavailable;
                }

                // stop all instances

                StorageUtil.DeletePlugin(bundle.Id);
                Configuration.Plugins.Remove(Configuration.PluginWithId(bundle.Id));

                StorageUtil.StorePlugin(bundle);
                Plugin plugin = new Plugin
                {
                    Id = bundle.Id,
                    Name = bundle.Name,
                    Description = bundle.Description,
                    Author = bundle.Author,
                    Assembly = bundle.Assembly,
                    ClassName = bundle.ClassName,
                    Created = bundle.Created,
                    Updated = bundle.Updated,
                    Configuration = bundle.Configuration,
                    Added = DateTime.Now
                };
                Configuration.Plugins.Add(plugin);
                Configuration.Save();

                EventLogger.LogInformation("Supervisor.UpdatePlugin", String.Format("Updated plugin '{0}'.", bundle.Id));

                bundle.Dispose();
            }
            catch (Exception exception)
            {
                EventLogger.LogException("Supervisor.UpdatePlugin", exception);
                return Result.UnknownError;
            }
            return Result.Success;
        }

        public Plugin GetPlugin (Guid pluginId)
        {
            try { return Configuration.PluginWithId(pluginId); }
            catch (Exception exception)
            {
                EventLogger.LogException("Supervisor.GetPlugin", exception);
                return null;
            }
        }

        public Plugin[] ListPlugins ()
        {
            try { return Configuration.Plugins.ToArray(); }
            catch (Exception exception)
            {
                EventLogger.LogException("Supervisor.ListPlugins", exception);
                return null;
            }
        }
        #endregion

        #region Solutions
        public Result AddSolution (byte[] data)
        {
            try
            {
                SolutionBundle bundle = new SolutionBundle(data);
                EventLogger.LogInformation("Supervisor.AddSolution", String.Format("Adding solution '{0}'.", bundle.Id));

                if (Configuration.SolutionWithId(bundle.Id) != null)
                {
                    EventLogger.LogError("Supervisor.AddSolution", String.Format("Solution '{0}' already exists.", bundle.Id));
                    return Result.SolutionIdAlreadyExists;
                }

                foreach (SolutionBundle.Service service in bundle.Services)
                {
                    if ((from s in Configuration.Services where s.Id == service.Id select s).FirstOrDefault() != null)
                    {
                        EventLogger.LogError("Supervisor.AddSolution", String.Format("Service with id '{0}' already exists.", service.Id));
                        return Result.ServiceIdAlreadyExists;
                    }

                    foreach (SolutionBundle.Instance instance in service.Instances)
                    {
                        if (Configuration.PluginWithId(instance.PluginId) == null)
                        {
                            EventLogger.LogError("Supervisor.AddSolution", String.Format("Plugin '{0}' does not exist.", instance.PluginId));
                            return Result.PluginIdUnavailable;
                        }
                    }
                }

                StorageUtil.StoreSolution(bundle);
                Solution solution = new Solution
                {
                    Id = bundle.Id,
                    Name = bundle.Name,
                    Description = bundle.Description,
                    Author = bundle.Author,
                    Created = bundle.Created,
                    Updated = bundle.Updated,
                    Added = DateTime.Now,
                    State = Solution.SolutionState.Retracted
                };
                Configuration.Solutions.Add(solution);
                foreach (var service in bundle.Services)
                {
                    Solution.Service newService = new Solution.Service
                    {
                        Id = service.Id,
                        Name = service.Name,
                        Description = service.Description,
                        Username = service.Username,
                        Password = service.Password,
                        SolutionId = solution.Id,
                        ManagementPort = 0,
                        State = Solution.Service.ServiceState.Deactivated
                    };
                    foreach (var instance in service.Instances)
                    {
                        Configuration.Instances.Add(new Solution.Instance
                        {
                            Id = Guid.NewGuid(),
                            PluginId = instance.PluginId,
                            Configuration = instance.Configuration,
                            ServiceId = newService.Id,
                            SolutionId = solution.Id,
                            State = Solution.Instance.InstanceState.Stopped
                        });
                    }
                    Configuration.Services.Add(newService);
                }
                Configuration.Save();

                EventLogger.LogInformation("Supervisor.AddSolution", String.Format("Added solution '{0}'.", bundle.Id));

                bundle.Dispose();
            }
            catch (Exception exception)
            {
                EventLogger.LogException("Supervisor.AddSolution", exception);
                return Result.UnknownError;
            }
            return Result.Success;
        }

        public Result RemoveSolution (Guid solutionId)
        {
            try
            {
                EventLogger.LogInformation("Supervisor.RemoveSolution", String.Format("Removing solution '{0}'.", solutionId));

                Solution solution = Configuration.SolutionWithId(solutionId);
                if (solution == null)
                {
                    EventLogger.LogError("Supervisor.RemoveSolution", String.Format("Solution '{0}' does not exist.", solutionId));
                    return Result.SolutionIdUnavailable;
                }

                if (solution.State == Solution.SolutionState.Deployed)
                    DeployUtil.RetractSolution(solution.Id, false);

                StorageUtil.DeleteSolution(solutionId);
                Configuration.Solutions.Remove(Configuration.SolutionWithId(solutionId));
                Configuration.Services.RemoveAll((service) => service.SolutionId == solutionId);
                Configuration.Instances.RemoveAll((instance) => instance.SolutionId == solutionId);
                Configuration.Save();

                EventLogger.LogInformation("Supervisor.RemoveSolution", String.Format("Solution '{0}' removed.", solutionId));
            }
            catch (Exception exception)
            {
                EventLogger.LogException("Supervisor.RemoveSolution", exception);
                return Result.UnknownError;
            }
            return Result.Success;
        }

        public Result UpdateSolution (byte[] data)
        {
            try
            {
                SolutionBundle bundle = new SolutionBundle(data);
                EventLogger.LogInformation("Supervisor.UpdateSolution", String.Format("Updating solution '{0}'.", bundle.Id));
                Solution solution = Configuration.SolutionWithId(bundle.Id);

                if (solution == null)
                {
                    EventLogger.LogError("Supervisor.UpdateSolution", String.Format("Solution '{0}' does not exist.", bundle.Id));
                    return Result.SolutionIdUnavailable;
                }

                bool deployed = false;

                if (solution.State == Solution.SolutionState.Deployed)
                {
                    deployed = true;
                    DeployUtil.RetractSolution(solution.Id, false);
                }

                StorageUtil.DeleteSolution(bundle.Id);
                Configuration.Solutions.Remove(Configuration.SolutionWithId(bundle.Id));
                Configuration.Services.RemoveAll((service) => service.SolutionId == bundle.Id);
                Configuration.Instances.RemoveAll((instance) => instance.SolutionId == bundle.Id);

                StorageUtil.StoreSolution(bundle);
                solution = new Solution
                {
                    Id = bundle.Id,
                    Name = bundle.Name,
                    Description = bundle.Description,
                    Author = bundle.Author,
                    Created = bundle.Created,
                    Updated = bundle.Updated,
                    Added = DateTime.Now,
                    State = Solution.SolutionState.Retracted
                };
                Configuration.Solutions.Add(solution);
                foreach (var service in bundle.Services)
                {
                    Solution.Service newService = new Solution.Service
                    {
                        Id = service.Id,
                        Name = service.Name,
                        Description = service.Description,
                        Username = service.Username,
                        Password = service.Password,
                        SolutionId = solution.Id,
                        ManagementPort = 0,
                        State = Solution.Service.ServiceState.Deactivated
                    };
                    foreach (var instance in service.Instances)
                    {
                        Configuration.Instances.Add(new Solution.Instance
                        {
                            Id = Guid.NewGuid(),
                            PluginId = instance.PluginId,
                            Configuration = instance.Configuration,
                            ServiceId = newService.Id,
                            SolutionId = solution.Id,
                            State = Solution.Instance.InstanceState.Stopped
                        });
                    }
                    Configuration.Services.Add(newService);
                }
                Configuration.Save();

                if (deployed)
                    DeployUtil.DeploySolution(solution.Id, false);

                EventLogger.LogInformation("Supervisor.UpdateSolution", String.Format("Updated solution '{0}'.", bundle.Id));

                bundle.Dispose();
            }
            catch (Exception exception)
            {
                EventLogger.LogException("Supervisor.UpdateSolution", exception);
                return Result.UnknownError;
            }
            return Result.Success;
        }

        public Solution GetSolution (Guid solutionId)
        {
            return Configuration.SolutionWithId(solutionId);
        }

        public Solution[] ListSolutions ()
        {
            return Configuration.Solutions.ToArray();
        }

        public Result DeploySolution (Guid solutionId)
        {
            try
            {
                EventLogger.LogInformation("Supervisor.DeploySolution", String.Format("Deploying solution '{0}'.", solutionId));
                Result result = DeployUtil.DeploySolution(solutionId, false);
                if (result != Result.Success)
                    return result;
                EventLogger.LogInformation("Supervisor.DeploySolution", String.Format("Deployed solution '{0}'.", solutionId));
            }
            catch (Exception exception)
            {
                EventLogger.LogException("Supervisor.DeploySolution", exception);
                return Result.UnknownError;
            }
            return Result.Success;
        }

        public Result RetractSolution (Guid solutionId)
        {
            try
            {
                EventLogger.LogInformation("Supervisor.RetractSolution", String.Format("Retracting solution '{0}'.", solutionId));
                Result result = DeployUtil.RetractSolution(solutionId, false);
                if (result != Result.Success)
                    return result;
                EventLogger.LogInformation("Supervisor.RetractSolution", String.Format("Retracted solution '{0}'.", solutionId));
            }
            catch (Exception exception)
            {
                EventLogger.LogException("Supervisor.RetractSolution", exception);
                return Result.UnknownError;
            }
            return Result.Success;
        }
        #endregion

        #region Instances
        public Solution.Instance[] ListInstances()
        {
            return Configuration.Instances.ToList().ToArray();
        }

        public Solution.Instance[] ListInstancesInSolution(Guid solutionId)
        {
            return Configuration.InstancesInSolution(solutionId).ToArray();
        }

        public Solution.Instance[] ListInstancesInService(Guid serviceId)
        {
            return Configuration.InstancesInService(serviceId).ToArray();
        }

        public Solution.Instance[] ListInstancesInPlugin(Guid pluginId)
        {
            return Configuration.InstancesInPlugin(pluginId).ToArray();
        }

        public Solution.Instance GetInstance(Guid instanceId)
        {
            return Configuration.InstanceWithId(instanceId);
        }
        #endregion

        #region Start & stop
        public void OnStart ()
        {
            EventLogger.LogInformation("Supervisor", "Supervisor starting");
            try
            {
                Configuration.Load();

                foreach (Solution solution in Configuration.Solutions)
                {
                    if (solution.State == Solution.SolutionState.Deployed)
                        DeployUtil.DeploySolution(solution.Id, true);
                }
            }
            catch (Exception exception)
            {
                EventLogger.LogException("Supervisor.OnStart", exception);
            }
            EventLogger.LogInformation("Supervisor", "Supervisor started");
        }

        public void OnStop ()
        {
            EventLogger.LogInformation("Supervisor", "Supervisor stopping");
            foreach (var solution in Configuration.Solutions)
            {
                try
                {
                    if (solution.State == Solution.SolutionState.Deployed)
                        DeployUtil.RetractSolution(solution.Id, true);
                }
                catch (Exception exception)
                {
                    EventLogger.LogException("Supervisor.OnStop", exception);
                }
            }
            EventLogger.LogInformation("Supervisor", "Supervisor stopped");
        }
        #endregion
    }
}
